frontend/pages/e/[uuid]/details.tsx (view raw)
1import moment from 'moment';
2import Tooltip from '@mui/material/Tooltip';
3import IconButton from '@mui/material/IconButton';
4import Box from '@mui/material/Box';
5import Link from '@mui/material/Link';
6import Card from '@mui/material/Card';
7import Container from '@mui/material/Container';
8import TextField from '@mui/material/TextField';
9import Typography from '@mui/material/Typography';
10import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
11import EventIcon from '@mui/icons-material/Event';
12import TuneIcon from '@mui/icons-material/Tune';
13import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
14import {useTheme} from '@mui/material/styles';
15import {DatePicker} from '@mui/x-date-pickers/DatePicker';
16import {PropsWithChildren, useState} from 'react';
17import {useTranslation} from 'react-i18next';
18import pageUtils from '../../../lib/pageUtils';
19import ShareEvent from '../../../containers/ShareEvent';
20import useEventStore from '../../../stores/useEventStore';
21import useToastStore from '../../../stores/useToastStore';
22import EventLayout, {TabComponent} from '../../../layouts/Event';
23import {
24 EventByUuidDocument,
25 useUpdateEventMutation,
26} from '../../../generated/graphql';
27import PlaceInput from '../../../containers/PlaceInput';
28
29interface Props {
30 eventUUID: string;
31 announcement?: string;
32}
33
34const Page = (props: PropsWithChildren<Props>) => {
35 return <EventLayout {...props} Tab={DetailsTab} />;
36};
37
38const DetailsTab: TabComponent = ({}) => {
39 const {t} = useTranslation();
40 const theme = useTheme();
41 const [updateEvent] = useUpdateEventMutation();
42 const addToast = useToastStore(s => s.addToast);
43 const setEventUpdate = useEventStore(s => s.setEventUpdate);
44 const event = useEventStore(s => s.event);
45 const [isEditing, setIsEditing] = useState(false);
46
47 const onSave = async e => {
48 try {
49 const {uuid, ...data} = event;
50 const {id, travels, waitingPassengers, __typename, ...input} = data;
51 await updateEvent({
52 variables: {
53 uuid,
54 eventUpdate: {
55 ...input,
56 },
57 },
58 refetchQueries: ['eventByUUID'],
59 });
60 setIsEditing(false);
61 } catch (error) {
62 console.error(error);
63 addToast(t('event.errors.cant_update'));
64 }
65 };
66
67 const modifyButton = isEditing ? (
68 <Tooltip
69 title={t('event.details.save')}
70 sx={{
71 position: 'absolute',
72 top: theme.spacing(2),
73 right: theme.spacing(2),
74 }}
75 >
76 <IconButton color="primary" onClick={onSave}>
77 <CheckCircleOutlineIcon />
78 </IconButton>
79 </Tooltip>
80 ) : (
81 <Tooltip
82 title={t('event.details.modify')}
83 sx={{
84 position: 'absolute',
85 top: theme.spacing(2),
86 right: theme.spacing(2),
87 }}
88 >
89 <IconButton color="primary" onClick={() => setIsEditing(true)}>
90 <TuneIcon />
91 </IconButton>
92 </Tooltip>
93 );
94
95 if (!event) return null;
96
97 return (
98 <Box
99 sx={{
100 position: 'relative',
101 }}
102 >
103 <Container
104 sx={{
105 p: 4,
106 mt: 6,
107 mb: 11,
108 mx: 0,
109 [theme.breakpoints.down('md')]: {
110 p: 2,
111 },
112 }}
113 >
114 <Card
115 sx={{
116 position: 'relative',
117 maxWidth: '100%',
118 width: '350px',
119 p: 2,
120 }}
121 >
122 <Typography variant="h4" pb={2}>
123 {t('event.details')}
124 </Typography>
125 {modifyButton}
126 <Box pt={2} pr={1.5}>
127 <Typography variant="overline">{t('event.fields.name')}</Typography>
128 <Typography variant="body1">
129 {isEditing ? (
130 <TextField
131 size="small"
132 fullWidth
133 value={event.name}
134 onChange={e => setEventUpdate({name: e.target.value})}
135 name="name"
136 id="EditEventName"
137 />
138 ) : (
139 <Typography variant="body1" id="EventName">
140 {event.name ?? t('event.fields.empty')}
141 </Typography>
142 )}
143 </Typography>
144 </Box>
145 <Box pt={2} pr={1.5}>
146 <Typography variant="overline">{t('event.fields.date')}</Typography>
147 {isEditing ? (
148 <Typography variant="body1">
149 <DatePicker
150 slotProps={{
151 textField: {
152 size: 'small',
153 id: `EditEventDate`,
154 fullWidth: true,
155 placeholder: t('event.fields.date_placeholder'),
156 },
157 }}
158 format="DD/MM/YYYY"
159 value={moment(event.date)}
160 onChange={date =>
161 setEventUpdate({
162 date: !date ? null : moment(date).format('YYYY-MM-DD'),
163 })
164 }
165 />
166 </Typography>
167 ) : (
168 <Box position="relative">
169 <Typography variant="body1" id="EventDate">
170 {event.date
171 ? moment(event.date).format('DD/MM/YYYY')
172 : t('event.fields.empty')}
173 </Typography>
174 <EventIcon
175 color="action"
176 sx={{
177 position: 'absolute',
178 right: theme.spacing(-0.5),
179 top: 0,
180 }}
181 />
182 </Box>
183 )}
184 </Box>
185 <Box pt={2} pr={1.5}>
186 <Typography variant="overline">
187 {t('event.fields.address')}
188 </Typography>
189 {isEditing ? (
190 <PlaceInput
191 place={event.address}
192 onSelect={({location, place}) => {
193 setEventUpdate({
194 address: place,
195 latitude: location[1],
196 longitude: location[0],
197 });
198 }}
199 />
200 ) : (
201 <Box position="relative">
202 <Typography variant="body1" id="EventAddress" sx={{pr: 3}}>
203 {event.address ? (
204 <Link
205 target="_blank"
206 rel="noreferrer"
207 href={`https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(
208 event.address
209 )}`}
210 onClick={e => e.preventDefault}
211 >
212 {event.address}
213 </Link>
214 ) : (
215 t('event.fields.empty')
216 )}
217 </Typography>
218 <PlaceOutlinedIcon
219 color="action"
220 sx={{
221 position: 'absolute',
222 right: theme.spacing(-0.5),
223 top: 0,
224 }}
225 />
226 </Box>
227 )}
228 </Box>
229 <Box pt={2} pr={1.5}>
230 <Typography variant="overline">
231 {t('event.fields.description')}
232 </Typography>
233 {isEditing ? (
234 <Typography variant="body1">
235 <TextField
236 fullWidth
237 multiline
238 maxRows={4}
239 inputProps={{maxLength: 250}}
240 value={event.description || ''}
241 onChange={e => setEventUpdate({description: e.target.value})}
242 id={`EditEventDescription`}
243 name="description"
244 />
245 </Typography>
246 ) : (
247 <Typography variant="body1" id="EventDescription" sx={{pr: 3}}>
248 {event.description ?? t('event.fields.empty')}
249 </Typography>
250 )}
251 </Box>
252 {!isEditing && (
253 <ShareEvent
254 title={`Caroster ${event.name}`}
255 sx={{width: '100%', mt: 2}}
256 />
257 )}
258 </Card>
259 </Container>
260 </Box>
261 );
262};
263
264export const getServerSideProps = pageUtils.getServerSideProps(
265 async (context, apolloClient) => {
266 const {uuid} = context.query;
267 const {host = ''} = context.req.headers;
268 let event = null;
269
270 // Fetch event
271 try {
272 const {data} = await apolloClient.query({
273 query: EventByUuidDocument,
274 variables: {uuid},
275 });
276 event = data?.eventByUUID?.data;
277 } catch (error) {
278 return {
279 notFound: true,
280 };
281 }
282
283 return {
284 props: {
285 eventUUID: uuid,
286 metas: {
287 title: event?.attributes?.name || '',
288 url: `https://${host}${context.resolvedUrl}`,
289 },
290 },
291 };
292 }
293);
294export default Page;